/**
 * <copyright>
 * Copyrigth 2007 Aurelien Moreau
 *
 * This file is part of ProcessShop.
 *
 * ProcessShop is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * ProcessShop is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with ProcessShop; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * </copyright>
 *
 * $Id$
 */
package net.yienyien.process.bpel.generator.partner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import net.yienyien.process.bpd.BPDObject;
import net.yienyien.process.bpd.BpdPackage;
import net.yienyien.process.bpd.EndEvent;
import net.yienyien.process.bpd.Event;
import net.yienyien.process.bpd.IntermediateEvent;
import net.yienyien.process.bpd.StartEvent;
import net.yienyien.process.bpd.Task;
import net.yienyien.process.bpel.generator.BPELGenerationException;
import net.yienyien.process.bpel.generator.Contrib;
import net.yienyien.process.bpel.generator.ContribImpl;
import net.yienyien.process.bpel.generator.Production;
import net.yienyien.process.bpel.generator.impl.ProductionImpl;
import net.yienyien.process.pmm.Binding;
import net.yienyien.process.pmm.Bindings;
import net.yienyien.process.pmm.PartnerBinding;
import net.yienyien.process.pmm.PmmPackage;
import net.yienyien.process.pmm.ProcessBinding;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.xml.type.util.XMLTypeUtil;
import org.eclipse.wst.wsdl.Definition;
import org.eclipse.wst.wsdl.Operation;
import org.eclipse.wst.wsdl.PortType;
import org.eclipse.wst.wsdl.WSDLPackage;
import org.open.oasis.docs.wsbpel._2._0.plnktype.PlnktypeFactory;
import org.open.oasis.docs.wsbpel._2._0.plnktype.PlnktypePackage;
import org.open.oasis.docs.wsbpel._2._0.plnktype.TPartnerLinkType;
import org.open.oasis.docs.wsbpel._2._0.plnktype.TRole;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.AbstractFactory;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TBoolean;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TImport;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TInvoke;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TOnMessage;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TPartnerLink;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TPartnerLinks;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TProcess;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TReceive;
import org.open.oasis.docs.wsbpel._2._0.process.abstract_.TReply;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

public class PartnerContrib extends ContribImpl implements Contrib {
	
	public static final String KEY="PartnerContrib";
	
	private ProductionImpl prod;
	private Bindings pmm;
	
	@Override
	public String getContribKey() {
		return KEY;
	}
	@Override
	public void prepare(Production data, EObject contribution) {
		prod=(ProductionImpl) data;
		pmm=(Bindings) contribution;
		
		CompositePmmManager l=new CompositePmmManager(data, (Bindings) contribution);
		getGenerator().getFolder().addListener(l);
	}
	
	@Override
	public void contrib() throws BPELGenerationException {
		partnerManagment(prod, pmm);
	}

	
	/*
	 * Name provider
	 */
	private static String EMPTY_NS="";
	
	private String newPartnerLinkTypeName(Production production, Operation op, String role) {
		return role+"PLT";
	}
		
	private String newRoleName(ProductionImpl production, Operation op, String role) {
		if(role==null) {
			return op.getName()+"Provider";
		} else {
			return role;
		}
	}
	
	private Map<String, Integer> plNames=new HashMap<String, Integer>();
	
	private String newPartnerLinkName(Production production, TPartnerLinkType plt, String role) {
		String base;
		if(plt.getName().toLowerCase().endsWith("plt")) {
			base= plt.getName().substring(0, plt.getName().length()-1);
		} else {
			base= plt.getName()+"PL";
		}
		Integer i=plNames.get(base);
		if(i==null) {
			plNames.put(base, new Integer(1));
		} else {
			i++;
		}
		
		return (i==null)?base:base+i;
	}
		
	/*
	 * 
	 */

	private void partnerManagment(ProductionImpl production, Bindings bindings) {
		TPartnerLinks pls=AbstractFactory.eINSTANCE.createTPartnerLinks();
		production.getProcess().setPartnerLinks(pls);
		
		for(Binding b: bindings.getBindings()) {
			initBinding(production, b);
		}
		
		for(Binding b: bindings.getBindings()) {
			resolveBinding(production, b);
		}
	}
	
	private void initBinding(Production production, Binding b) {
		if(PmmPackage.eINSTANCE.getProcessBinding().isInstance(b)) {
			ProcessBinding pb=(ProcessBinding) b;
			if(BpdPackage.eINSTANCE.getStartEvent().isInstance(pb.getEvent())) {
				Definition processWsdl=b.getOperation().getEnclosingDefinition();
				production.setProcessWSDL(processWsdl);
				production.getProcess().setName(b.getOperation().getName());
				production.getProcess().setTargetNamespace(processWsdl.getTargetNamespace());
				production.setTargetOperation(b.getOperation());
				return;
			}
		}
	}
	
	private void resolveBinding(ProductionImpl production,Binding b) {
		Definition wsdl=b.getOperation().getEnclosingDefinition();
		Operation op=b.getOperation();
		String role=b.getRole();
		
		if(PmmPackage.eINSTANCE.getPartnerBinding().isInstance(b)) {
			resolveBinding(production, wsdl, op, role, (PartnerBinding) b);	
		} else if(PmmPackage.eINSTANCE.getProcessBinding().isInstance(b)) {
			resolveBinding(production, wsdl, op, role, (ProcessBinding) b);
		}
	}
	
	private void resolveBinding(ProductionImpl prod, Definition wsdl, Operation op, String role, PartnerBinding b) {
		Task task=b.getTask();
		role=newRoleName(prod, op, role);
		
		
		TPartnerLinkType plt=resolvePartnerLinkType(prod, op, role);
		TPartnerLink     pl =resolvePartnerLink    (prod, plt, role);
						
		PortType pt=getEnclosingPortType(op);
	
		createInvoke(prod, task, pl, pt, op );
	}
		
	private void resolveBinding(ProductionImpl prod, Definition wsdl, Operation op, String role, ProcessBinding b) {
		Event event=b.getEvent();
		role=newRoleName(prod, op, role);
		
		
		TPartnerLinkType plt=resolvePartnerLinkType(prod, op, role);
		TPartnerLink     pl =resolveMyLink         (prod, plt, role);
		
		PortType pt=getEnclosingPortType(op);
		
		if(BpdPackage.eINSTANCE.getStartEvent().isInstance(b.getEvent())) {
			createReceive(prod, (StartEvent) event, pl, pt, prod.getTargetOperation());
		} else if(BpdPackage.eINSTANCE.getIntermediateEvent().isInstance(b.getEvent())) {
			createOnMessage(prod, (IntermediateEvent) event, pl, pt, b.getOperation());
		} else if(BpdPackage.eINSTANCE.getEndEvent().isInstance(b.getEvent())) {
			createReply(prod, (EndEvent) event, pl, pt, prod.getTargetOperation());
		}
	}

	private TPartnerLink resolveMyLink(Production prod, TPartnerLinkType plt, String role) {
		Object pltName=XMLTypeUtil.createQName(plt.getEnclosingDefinition().getTargetNamespace(), plt.getName(), EMPTY_NS);
		for(TPartnerLink pl :prod.getProcess().getPartnerLinks().getPartnerLink()) {			
			if(pltName.equals(pl.getPartnerLinkType()) && pl.getMyRole().equals(role)) {
				return pl;
			}
		}
		return createMyLink(prod, plt, role);
	}
	
	private TPartnerLink resolvePartnerLink(Production prod, TPartnerLinkType plt, String role) {
		Object pltName=XMLTypeUtil.createQName(plt.getEnclosingDefinition().getTargetNamespace(), plt.getName(), EMPTY_NS);
		for(TPartnerLink pl :prod.getProcess().getPartnerLinks().getPartnerLink()) {			
			if(pltName.equals(pl.getPartnerLinkType()) && pl.getPartnerRole().equals(role)) {
				return pl;
			}
		}
		return createPartnerLink(prod, plt, role);
	}

	private TPartnerLinkType resolvePartnerLinkType(Production prod, Operation op, String role) {
		Definition wsdl=op.getEnclosingDefinition();
		ArrayList<TPartnerLinkType> plts=new ArrayList<TPartnerLinkType>();
		top :for(Object  ee: wsdl.getExtensibilityElements()) {
			if(PlnktypePackage.eINSTANCE.getTRole().isInstance(ee)) {
				TPartnerLinkType plt=(TPartnerLinkType) ee;
				for(TRole r:plt.getRole()) {
					if(!role.equals(r.getName())) {
						continue;
					}
					if(getEnclosingPortType(op)!=null) {
						plts.add(plt);
						continue top;
					}
				}
			}
		}
		
		if(plts.size()==0) {
			return createPartnerLinkType(prod, op, role);
		} else if(plts.size()==1) {
			return plts.get(0);
		} else if(plts.size()>1) {
			throw new NotImplementedException();
		} else {
			assert(false);
			return null;
		}
	}

	
	private TPartnerLink     createMyLink(Production prod, TPartnerLinkType plt, String role) {
		TPartnerLink pl=AbstractFactory.eINSTANCE.createTPartnerLink();
		
		pl.setPartnerLinkType(XMLTypeUtil.createQName(getPartnerLinkTypeNamespaceURI(prod.getProcess(), plt), plt.getName(), EMPTY_NS));
		pl.setName(newPartnerLinkName(prod, plt, role));
		pl.setMyRole(role);
		
		prod.getProcess().getPartnerLinks().getPartnerLink().add(pl);
		return pl;
	}
	
	private TPartnerLink     createPartnerLink(Production prod, TPartnerLinkType plt, String role) {
		TPartnerLink pl=AbstractFactory.eINSTANCE.createTPartnerLink();
		
		pl.setPartnerLinkType(XMLTypeUtil.createQName(getPartnerLinkTypeNamespaceURI(prod.getProcess(), plt), plt.getName(), EMPTY_NS));
		pl.setName(newPartnerLinkName(prod, plt, role));
		pl.setPartnerRole(role);
		
		prod.getProcess().getPartnerLinks().getPartnerLink().add(pl);
		return pl;
	}
	

	
	private TPartnerLinkType createPartnerLinkType(Production prod, Operation op, String role) {
		Definition wsdl= op.getEnclosingDefinition();
		TPartnerLinkType plt=PlnktypeFactory.eINSTANCE.createTPartnerLinkType();
		
		plt.setName(newPartnerLinkTypeName(prod, op, role));
		
		TRole nrole=PlnktypeFactory.eINSTANCE.createTRole();
		plt.getRole().add(nrole);
		
		nrole.setName(role);
		nrole.setPortType(getEnclosingPortType(op).getQName());
		
		prod.getWSDLs().add(wsdl);
		createImport(prod, wsdl.getTargetNamespace(), "http://schemas.xmlsoap.org/wsdl/");
		
		wsdl.addExtensibilityElement(plt);
		return plt;
	}

	/*
	 * Tools
	 */
	private PortType getEnclosingPortType(Operation op) {
		for(Object ePt: op.getEnclosingDefinition().getEPortTypes()) {
			PortType pt = (PortType) ePt;
			for(Object eOp: pt.getEOperations()) {
				if(eOp.equals(op)) {
					return pt;
				}
			}
		}
		return null;
	}
	
	private String getPartnerLinkTypeNamespaceURI(TProcess process, TPartnerLinkType plt) {
		if(WSDLPackage.eINSTANCE.getDefinition().isInstance(plt.eContainer())) {
			Definition def=(Definition) plt.eContainer();
			return def.getTargetNamespace();
		} else if(plt.eContainer()==null) {
			return process.getTargetNamespace().toString();
		} else {
			assert(false);
			return null;
		}
	}
	
	/*
	 * Partner connection
	 * Object opQname=XMLTypeUtil.createQName(op.getEnclosingDefinition().getTargetNamespace(), op.getName(), EMPTY_NS);
	 */
	
	private HashMap<String, TImport> importNs=new HashMap<String, TImport>();
	private TImport createImport(Production prod, String ns, String type) {
		TImport import_=importNs.get(ns);
		if(import_==null) {
			import_=AbstractFactory.eINSTANCE.createTImport();
			importNs.put(ns, import_);
			import_.setNamespace(ns);
			import_.setImportType(type);
			prod.getProcess().getImport().add(import_);
		}
		return import_;
	}
	
	
	private TInvoke createInvoke(Production prod, Task task, TPartnerLink pl, PortType pt, Operation op) {
		TInvoke invoke=AbstractFactory.eINSTANCE.createTInvoke();
		
		invoke.setPartnerLink(pl.getName());
		invoke.setPortType(XMLTypeUtil.createQName(pt.getQName().getNamespaceURI(), pt.getQName().getLocalPart(), EMPTY_NS));
		
		invoke.setOperation(op.getName());
	
		prod.getOperationBindings().put(invoke, op);
		
		prod.getTaskMappings().put(task, invoke);
		return invoke;
	}
	
	private TReceive createReceive(Production prod, StartEvent e, TPartnerLink pl, PortType pt, Operation op) {
		TReceive receive=AbstractFactory.eINSTANCE.createTReceive();
		
		receive.setPartnerLink(pl.getName());
		receive.setPortType(XMLTypeUtil.createQName(pt.getQName().getNamespaceURI(), pt.getQName().getLocalPart(), EMPTY_NS));
		
		receive.setOperation(op.getName());
		
		prod.getOperationBindings().put(receive, op);
		
		receive.setCreateInstance(TBoolean.YES);
		
		prod.getTaskMappings().put(e, receive);
		return receive;
	}
	
	private TReply createReply(Production prod, EndEvent e, TPartnerLink pl, PortType pt, Operation op) {
		TReply reply=AbstractFactory.eINSTANCE.createTReply();
		
		reply.setPartnerLink(pl.getName());
		reply.setPortType(XMLTypeUtil.createQName(pt.getQName().getNamespaceURI(), pt.getQName().getLocalPart(), EMPTY_NS));
		
		reply.setOperation(op.getName());
		
		prod.getOperationBindings().put(reply, op);
		
		prod.getTaskMappings().put(e, reply);
		return reply;
	}
	
	private TOnMessage createOnMessage(Production prod, IntermediateEvent e, TPartnerLink pl, PortType pt, Operation op) {
		TOnMessage onMessage=AbstractFactory.eINSTANCE.createTOnMessage();
		
		onMessage.setPartnerLink(pl.getName());
		onMessage.setPortType(XMLTypeUtil.createQName(pt.getQName().getNamespaceURI(), pt.getQName().getLocalPart(), EMPTY_NS));
		
		onMessage.setOperation(op.getName());
		
		prod.getOperationBindings().put(onMessage, op);
		
		prod.getTaskMappings().put(e, onMessage);
		return onMessage;
	}
	
	/* Unuesed
	private TRole findRole(TPartnerLinkType plt, String role) {
		TRole prole=null;
		
		if(plt.getRole().size()==1) {
			prole=plt.getRole().get(0);
		} else if(role!=null){
			for(TRole r: plt.getRole()) {
				if(r.getName().equals(role)) {
					prole=r;
					break;
				}
			}
		} else {
			assert(false);
		}
		return prole;
	}
	*/
}
